import hmac
import hashlib
import base64
import json
from uuid import UUID
from urllib.parse import urlencode, urlparse
from aiohttp import ClientSession, WSMsgType

from time import mktime
from datetime import datetime
from wsgiref.handlers import format_date_time

from abc import ABC, abstractmethod

# class MixinGenParams(object):
    # '''讯飞云星火认知大模型请求参数生成工厂类'''
    # pass

class BaseXFYunHandler(ABC):
    '''讯飞星火大模型api操作抽象基类'''
    app_id = str
    api_key = str
    api_secret = str
    url = str
    scheme = str
    host = str
    path = str
    method = str
    session = None  # 内部请求session
    request_Protocol = None #内部请求协议
    response_Protocol = None    # 响应协议
    headers = {"Content-Type": "application/json;charset=UTF-8"}
    __wss_schemes = ("wss", "ws")
    __https_schemes = ("https", "http")

    def __init__(self, 
                 app_id:str,
                 api_key:str,
                 api_secret:str,
                 api_url:str,
                 response_protocol:any = None,
                 session:ClientSession = None,
                 ):
        self.app_id = app_id  # 
        self.api_key = api_key #
        self.api_secret = api_secret#
        self.url = api_url # 请求的url 
        self.response_Protocol = response_protocol# 发出去的方法
        self.session = session # 

        self.scheme = urlparse(self.url).scheme  # 请求的协议 wss or https
        self.host = urlparse(self.url).netloc 
        self.path = urlparse(self.url).path
        
        self.request_Protocol = None #请求大模型api的方法
        
    async def async_connect(self) -> None:
        '''创建连接wss|https'''
        if self.session is None:
            self.session = ClientSession()
        if self.scheme in ["wss","ws"]:
            self.request_Protocol = await self.session.ws_connect(self.request_url)

    async def async_send_str(self, query: str, **kwargs) -> None:
        '''发送字符串数据'''
        be_send_data = self.gen_params(query, **kwargs) # 组装数据
        if self.scheme in self.__wss_schemes: # 通过websocket发送
            await self.request_Protocol.send_str(json.dumps(be_send_data))
        elif self.scheme in self.__https_schemes: # 通过 http 发送数据
            self.request_Protocol = await self.session.post(self.request_url, json=be_send_data, headers = self.headers)
        else:
            pass

    async def async_send_json(self, query: dict, **kwargs) -> None:
        '''发送json数据'''
        if self.scheme in self.__wss_schemes:
            await self.request_Protocol.send_json()
        elif self.scheme in self.__https_schemes:
            self.request_Protocol = await self.session.post(self.request_url, json=query, headers = self.headers)
        else:
            pass

    async def async_send_bytes(self, query: bytes, **kwargs) -> None:
        # return await super().async_send_bytes(query, user_id)
        pass

    async def async_recv_data(self, callback: any = None) -> None:
        '''
        接收大模型api的响应数据

        args:
            callback : function|method  响应数据处理回调,要求有三个参数:
                args:
                    reply : any 响应数据
                    send_process : any 数据输出方法. 该参数是在实例化对象时传输给 self.response_Protocol 参数
                            eg : send_process = print,  print(reply)输出到控制台. send_process = ws, await ws.send_str(reply) 使用websocket发送
                    ins : any 调用该回调的对象或者连接池, 以便在接收数据异常或接收完成后关闭连接. eg:
                        callbakc(obj = self): 
                                await self.session.close()    
        '''
        if self.scheme in self.__wss_schemes:  # websocket响应数据
            await self.__async_handle_ws_data(callback)
        elif self.scheme in self.__https_schemes:   # http 响应数据
            await self.__async_handle_http_data(callback)

    async def async_close_request_Protocol(self) -> None:
        '''关闭连接'''
        if hasattr(self.request_Protocol, 'close'):
            await self.request_Protocol.close()
            self.request_Protocol = None

    async def async_close_session(self) -> None:
        # return await super().async_close_session()
        '''关闭session'''
        if hasattr(self.session, "close"):
            await self.session.close()
            self.session = None

    async def __async_handle_ws_data(self, callback:any = None):
        '''
         接收websocket返回的数据,使用回调函数处理数据。
         只专注接收，响应给客户端交由回调来做
        '''
        async for msg in self.request_Protocol:
            if msg.type == WSMsgType.CLOSED:
                break
            if callback:
                await callback(msg, self.response_Protocol, self)
        await self.async_close_request_Protocol()

    async def __async_handle_http_data(self, callback:any = None):
        '''
         接收http返回的数据,使用回调函数处理数据。
         只专注接收，响应给客户端交由回调来做
        '''
        while True:
            chunk = await self.request_Protocol.content.readany()
            if not chunk:
                break
            data = chunk.decode("utf-8")
            if "data: [DONE]" in data :
                data = data.replace("data: [DONE]", "") #.rstrip() #去掉最后帧的结尾字符
            start_str = "data: "
            start_str_len = len(start_str)
            index = data.find(start_str)
            data = data[index+start_str_len:] #去掉开头的data字段
            if callback:
                await callback(data, self.response_Protocol, self)
            # 接收完了，提前结束循环
            if b'data: [DONE]' in chunk:
                break
        # await self.async_close_request_Protocol()
        await self.async_close_session()


    @abstractmethod
    def gen_params(self,query:any, **kwargs) -> dict:
        '''该类在子类中必须重写'''
        pass

    def get_conversation_context(self) -> list:
        '''获取对话上下文问答信息'''
        return list()

    @property
    @abstractmethod
    def request_url(self) -> str:
        '''
        实际请求的url.
        子类中重写.
        一般uri中带鉴权的.返回self.__cerate_auth_url.不带的直接返回self.url
        详细看讯飞文档https://www.xfyun.cn/doc/,
        '''
        pass

    def create_auth_url(self) -> str:
        """
        创建科大讯飞的鉴权url,
        https://www.xfyun.cn/doc/spark/spark_zh_iat.html
        """

        # 生成RFC1123格式的时间戳
        now = datetime.now()
        date = format_date_time(mktime(now.timetuple()))
        # # 检测并修改 self.host
        # if self.host == "tts-api.xfyun.cn":
        #     self.host = "ws-api.xfyun.cn"

        signature_origin = F"host: {self.host}\ndate: {date}\n{self.method} {self.path} HTTP/1.1"
        # 进行hmac-sha256进行加密
        signature_sha = hmac.new(
            self.api_secret.encode("utf-8"),
            signature_origin.encode("utf-8"),
            digestmod=hashlib.sha256,
        ).digest()
        signature_sha = base64.b64encode(signature_sha).decode(encoding="utf-8")

        authorization_origin = F'api_key="{self.api_key}", algorithm="hmac-sha256", headers="host date request-line", signature="{signature_sha}"'

        authorization = base64.b64encode(authorization_origin.encode("utf-8")).decode(
            encoding="utf-8"
        )
        # 将请求的鉴权参数组合为字典
        v = {"authorization": authorization, "date": date, "host": self.host}
        # 拼接鉴权参数，生成url
        # url = self.url + "?" + urlencode(v)
        url = F"{self.url}?{urlencode(v)}"
        # print("date: ", date)
        # print("v: ", v)
        # 此处打印出建立连接时候的url,参考本demo的时候可取消上方打印的注释，比对相同参数时生成的url与自己代码生成的url是否一致
        # print("websocket url :", url)
        return url